package calao.storage.bdb;

import static calao.util.Logger.DEBUG;
import static calao.util.Logger.log;

import java.util.Set;

import calao.storage.Field;
import calao.storage.Index;

import com.sleepycat.bind.tuple.TupleInput;
import com.sleepycat.bind.tuple.TupleOutput;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.SecondaryConfig;
import com.sleepycat.je.SecondaryDatabase;
import com.sleepycat.je.SecondaryKeyCreator;
import com.sleepycat.je.SecondaryMultiKeyCreator;

public class IndexDatabase 
implements SecondaryMultiKeyCreator, SecondaryKeyCreator
{
	
	final TableDatabase tableDB;
	final Index index;
	final SecondaryDatabase sdb;
	
	// TODO rework key maker to avoid multiple reads of the same record 
	class KeyMaker extends RecordReader {
		final Index primaryIndex = tableDB.primaryIndex;
		final KeyWriter keyWriter = new KeyWriter(tableDB, index);
		final DatabaseEntry entryKey;
		final DatabaseEntry entry;
		Field keyField;
		int valueBlockCount;
		
		KeyMaker(DatabaseEntry entryKey, DatabaseEntry entry) {
			super(tableDB.table);
			this.entryKey = entryKey;
			this.entry = entry;
		}
		TupleOutput[] makeKeys()
		throws DatabaseException 
		{
			for (int i=0; i<index.getFieldCount(); i++) {
				keyField = index.getField(i);
				if (primaryIndex.refersTo(keyField)) {
					keyWriter.setValueCount(keyField, 1);
					readPrimaryKey(entryKey);
				}
				else {
					valueBlockCount = 0;
					readData(entry);
					if (valueBlockCount==0) return new TupleOutput[0];
				}
			}
			return keyWriter.keys;
		}
		@Override
		boolean skipsField()
		{
			return field!=keyField;
		}
		@Override
		boolean onRead(Object value) {
			if (value!=null) {
				// case of primary key index field value
				keyWriter.writeValue(field, value);
			}
			return true;
		}
		@Override
		protected boolean readValueBlock(TupleInput in)
		{
			if (valueBlockCount++==0) {
				keyWriter.setValueCount(field, numFieldValues);
			}
			keyWriter.writeValueFrom(field, in);
			return valueBlockCount==numFieldValues;
		}
	}
	
	IndexDatabase(TableDatabase tableDB, final Index index) 
	throws DatabaseException
	{
		this.tableDB = tableDB;
		this.index = index;
		SecondaryConfig secConfig = new SecondaryConfig();
		secConfig.setAllowCreate(true);
		secConfig.setSortedDuplicates(!index.isKeyIndex());
		if (index.getFirstField().isMultiValued()) {
			secConfig.setMultiKeyCreator(this);
		}
		else {
			secConfig.setKeyCreator(this);
		}
		String sdbName = getDbName(tableDB.db.getDatabaseName(), index);
		sdb = BdbStorage.env.openSecondaryDatabase(null, sdbName.toString(), 
				tableDB.db, secConfig);
		log(DEBUG, "Index database %s opened", sdbName.toString());
	}
	
	static String getDbName(String tableDbName, Index index)
	{
		StringBuffer sdbName = null;
		for (Field field : index) {
			if (sdbName==null) {
				sdbName = new StringBuffer(tableDbName);
				sdbName.append("/");
			}
			else sdbName.append("+");
			sdbName.append(field.getName());
		}
		return sdbName.toString();
	}
	
	static void removeDatabase(BdbStorage storage, int dbId, Index index)
	throws DatabaseException
	{
		String dbName = getDbName(TableDatabase.getDbName(storage, dbId), index);
		BdbStorage.env.removeDatabase(null, dbName);
		log(DEBUG, "Database %s removed", dbName);		
	}

	@SuppressWarnings("unchecked")
	public void createSecondaryKeys(SecondaryDatabase sdb,
			DatabaseEntry ekey, DatabaseEntry edata, Set result)
	throws DatabaseException {
		for (TupleOutput key : new KeyMaker(ekey, edata).makeKeys()) {
			result.add(Util.toEntry(key));
		}
	}

	public boolean createSecondaryKey(SecondaryDatabase sdb, 
			DatabaseEntry ekey, DatabaseEntry edata, DatabaseEntry result) 
	throws DatabaseException {
		for (TupleOutput key : new KeyMaker(ekey, edata).makeKeys()) {
			result.setData(key.getBufferBytes(), 
					key.getBufferOffset(), key.getBufferLength());
			return true;
		}
		return false;
	}

}
